﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Security;
using System.Data;
using RAD.LightOne.Data;
using System.Configuration.Provider;
using System.Diagnostics;
using System.Reflection;

namespace RAD.LightOne.Web.Security
{
	#region Script

	/*
	 * 
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO
	 
CREATE TABLE [dbo].[Roles](
	[Id] [uniqueidentifier] NOT NULL,
	[Class] [varchar](256) NOT NULL,
	[Register] [datetime] NOT NULL,
	[Rolename] [varchar](50) NOT NULL,
	[ApplicationName] [varchar](50) NOT NULL,
	[Parent] [uniqueidentifier] NULL,
	[Alias] [varchar](50) NOT NULL,
 CONSTRAINT [PK_Roles] PRIMARY KEY CLUSTERED 
(
	[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

SET ANSI_PADDING OFF
GO

ALTER TABLE [dbo].[Roles]  WITH CHECK ADD  CONSTRAINT [FK_Roles_Roles] FOREIGN KEY([Parent])
REFERENCES [dbo].[Roles] ([Id])
GO

ALTER TABLE [dbo].[Roles] CHECK CONSTRAINT [FK_Roles_Roles]
GO
	 
CREATE TABLE [dbo].[UsersRoles](
	[UserId] [uniqueidentifier] NOT NULL,
	[RoleId] [uniqueidentifier] NOT NULL,
 CONSTRAINT [PK_UsersRoles] PRIMARY KEY CLUSTERED 
(
	[UserId] ASC,
	[RoleId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

ALTER TABLE [dbo].[UsersRoles]  WITH CHECK ADD  CONSTRAINT [FK_UsersRoles_Roles] FOREIGN KEY([RoleId])
REFERENCES [dbo].[Roles] ([Id])
ON UPDATE CASCADE
ON DELETE CASCADE
GO

ALTER TABLE [dbo].[UsersRoles] CHECK CONSTRAINT [FK_UsersRoles_Roles]
GO

ALTER TABLE [dbo].[UsersRoles]  WITH CHECK ADD  CONSTRAINT [FK_UsersRoles_Users] FOREIGN KEY([UserId])
REFERENCES [dbo].[Users] ([Id])
ON UPDATE CASCADE
ON DELETE CASCADE
GO

ALTER TABLE [dbo].[UsersRoles] CHECK CONSTRAINT [FK_UsersRoles_Users]
GO

	 */
	#endregion
	#region Documentation
	/// <summary>
	/// Implementação de regra
	/// </summary>
	/// <seealso cref="http://msdn.microsoft.com/en-us/library/317sza4k(v=vs.100).aspx"/>
	#endregion
	[ClassName("Class")]
	[LastPersistence("Register")]
	[DBTable("Roles")]
	public sealed class Role : MyObject<Role>
	{
		#region Propoerties

		#region Documentation
		/// <summary>
		/// Nome de sistema da regra
		/// </summary>
		#endregion
		[DBField("Rolename")]
		public string RoleName { get; set; }

		#region Documentation
		/// <summary>
		/// Nome da aplicação onde a regra é aplicável
		/// </summary>
		#endregion
		[DBField("ApplicationName")]
		public string ApplicationName { get; set; }

		#region Documentation
		/// <summary>
		/// Regra de origem ou container.
		/// </summary>
		#endregion
		[DBField("Parent")]
		public Role Parent { get; set; }

		#region Documentation
		/// <summary>
		/// Nome de exibição da regra.
		/// </summary>
		#endregion
		[DBField("Alias")]
		public string Alias { get; set; }
		#endregion

		#region Constructors

		public Role() : base() { }

		public Role(string applicationName, string roleName, string alias)
			: this()
		{
			this.ApplicationName = applicationName;
			this.RoleName = roleName;
			this.Alias = alias;
		}

		public Role(string applicationName, string roleName, string alias, Role parent)
			: this(applicationName, roleName, alias)
		{
			this.Parent = parent;
		}
		#endregion

		#region Methods

		public static bool InUse(Role role)
		{
			if (role == null) throw new ArgumentNullException("role");

			return
				Convert.ToInt32(
				DBEngine.GetInstance().ExecuteScalar("select count(*) from UsersRoles where Id = {0}", role)
				) > 0;
		}

		public static bool InUse(string roleName, string applicationName)
		{
			return
				Convert.ToInt32(
				DBEngine.GetInstance().ExecuteScalar("select count(*) from UsersRoles inner join Roles on Id = RoleId and Rolename ={0} and ApplicationName = {1}", roleName, applicationName)
				) > 0;
		}

		#region Documentation
		/// <summary>
		/// Registra a regra na aplicação.
		/// </summary>
		#endregion
		public static void Register(string roleName, string alias, string applicationName, Role parent = null)
		{
			if (string.IsNullOrEmpty(roleName)) throw new ArgumentNullException("role");
			if (string.IsNullOrEmpty(applicationName)) throw new ArgumentNullException("alias");

			using (IDbTransaction trx = DBEngine.GetInstance().BeginTransaction())
			{
				try
				{
					if (!Exist(trx, roleName, applicationName))
					{
						var newRole = new Role(applicationName, roleName, alias, parent);
						newRole.Save(trx);
					}
					trx.Commit();
				}
				catch
				{
					trx.Rollback();
				}
			}
		}

		#region Documentation
		/// <summary>
		/// Remove a regra da aplicação
		/// </summary>
		#endregion
		public static void UnRegister(Role role)
		{
			DBEngine.GetInstance().ExecuteNonQuery("DELETE From Roles where id={0}", role);
		}

		#region Documentation
		/// <summary>
		/// Remove a regra da aplicação
		/// </summary>
		#endregion
		public static void UnRegister(string roleName, string applicationName)
		{
			DBEngine.GetInstance().ExecuteNonQuery("DELETE From Roles where rolename={0} and applicationName = {1}", roleName, applicationName);
		}


		public static bool Exist(string name, string applicationName)
		{
			var dbe = DBEngine.GetInstance();
			return Convert.ToInt32(dbe.ExecuteScalar("select count(*) from roles where rolename = {0} and applicationname = {1}", name, applicationName)) > 0;
		}

		public static bool Exist(IDbTransaction trx, string name, string applicationName)
		{
			var dbe = DBEngine.GetInstance(trx);
			return Convert.ToInt32(dbe.ExecuteScalar("select count(*) from roles where rolename = {0} and applicationname = {1}", name, applicationName)) > 0;
		}

		public static Role FindByName(string roleName, string applicationName)
		{
			return Role.FindBySql("select Id, Class from Roles where rolename={0} and applicationname = {1}", roleName, applicationName).FirstOrDefault();
		}

		public static Role[] FindByUser(LightOneMembershipUser userData)
		{
			if (userData == null) throw new ArgumentNullException("userData");
			return Role.FindBySql("select Id, Class from Roles inner join UsersRoles on Roles.Id = UsersRoles.RoleId where UserId = {0}", userData.ProviderUserKey);
		}

		public static LightOneMembershipUser[] GetUsersByRole(Role role)
		{
			if (role == null) throw new ArgumentNullException("role");
			var all = MembershipUserModel.FindBySql("select Id, Class from Users inner join UsersRoles on Users.Id = UsersRoles.UserId where RoleId = {0}", role);
			LightOneMembershipUser[] users = new LightOneMembershipUser[all.Count()];
			for (int i = 0; i < users.Length; i++)
			{
				users[i] = all[i];
			}
			return users;
		}

		public static LightOneMembershipUser[] GetUsersByRole(string roleName, string applicationName)
		{
			var all = MembershipUserModel.FindBySql("select U.Id, U.Class from Users U inner join UsersRoles UR on U.Id = UR.UserId inner join Roles R on R.Id = UR.RoleId where R.Rolename = {0} and R.ApplicationName = {1}", roleName, applicationName);
			LightOneMembershipUser[] users = new LightOneMembershipUser[all.Count()];
			for (int i = 0; i < users.Length; i++)
			{
				users[i] = all[i];
			}
			return users;
		}

		protected override void OnSaving(System.Data.IDbTransaction trx)
		{
			//garantindo integridade referencial na mesma transação.
			base.OnSaving(trx);
			if (this.Parent != null && this.Parent.Id == Guid.Empty)
			{
				this.Parent.Save(trx);
			}
		}

		internal new void Save()
		{
			base.Save();
		}

		internal new void Save(System.Data.IDbTransaction trx)
		{
			base.Save(trx);
		}

		internal new void Load()
		{
			base.Load();
		}

		internal new void Load(System.Data.IDbTransaction trx)
		{
			base.Load(trx);
		}

		internal new void Delete()
		{
			base.Delete();
		}

		internal new void Delete(System.Data.IDbTransaction trx)
		{
			base.Delete(trx);
		}
		#endregion

	}

	#region Documentation
	/// <summary>
	/// Provedor de regras para o LightOne
	/// </summary>
	/// <remarks>Baseado em: http://msdn.microsoft.com/en-us/library/317sza4k(v=vs.100).aspx
	/// </remarks>
	#endregion
	public sealed class LightOneRoleProvider : RoleProvider
	{
		#region Fields
		private const string eventSource = "LightOneRoleProvider";
		private const string eventLog = "Application";
		private const string exceptionMessage = "An exception occurred. Please check the Event Log.";

		private bool pWriteExceptionsToEventLog = false;
		#endregion

		#region Properties
		public bool WriteExceptionsToEventLog
		{
			get { return pWriteExceptionsToEventLog; }
			set { pWriteExceptionsToEventLog = value; }
		}

		public override string ApplicationName
		{
			get;
			set;
		}
		#endregion

		public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
		{
			if (config == null)
				throw new ArgumentNullException("config");

			if (name == null || name.Length == 0)
				name = "LightOneRoleProvider";

			if (String.IsNullOrEmpty(config["description"]))
			{
				config.Remove("description");
				config.Add("description", "LightOne Role provider");
			}

			base.Initialize(name, config);

			if (config["applicationName"] == null || config["applicationName"].Trim() == "")
			{
				this.ApplicationName = System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath;
			}
			else
			{
				this.ApplicationName = config["applicationName"];
			}


			if (config["writeExceptionsToEventLog"] != null)
			{
				if (config["writeExceptionsToEventLog"].ToUpper() == "TRUE")
				{
					pWriteExceptionsToEventLog = true;
				}
			}
		}

		[Obsolete("Prefer other overload with strong typed parameters", false)]
		public override void AddUsersToRoles(string[] usernames, string[] roleNames)
		{

			const string sql = @"insert into UsersRoles(UserId,RoleId) values(
							(select top 1 Id from users where username = {1} and applicationname = {0}),
							(select top 1 Id from roles where Rolename = {2} and ApplicationName = {0}))";

			using (DBEngine dbe = DBEngine.GetInstance())
			{
				using (IDbTransaction trx = dbe.BeginTransaction())
				{
					try
					{
						foreach (var username in usernames)
						{
							foreach (var role in roleNames)
							{
								if (!this.IsUserInRole(trx, username, role))
								{
									dbe.ExecuteNonQuery(sql, this.ApplicationName, username, role);
								}
							}
						}
						trx.Commit();
					}
					catch (Exception except)
					{
						trx.Rollback();
						this.TreatException(except, MethodBase.GetCurrentMethod().Name);
					}
				}
			}

		}

		public void AddUsersToRoles(LightOneMembershipUser[] users, Role[] roles)
		{
			const string sql = @"insert into UsersRoles(UserId,RoleId) values({0},{1})";

			using (DBEngine dbe = DBEngine.GetInstance())
			{
				using (IDbTransaction trx = dbe.BeginTransaction())
				{
					try
					{
						foreach (var user in users)
						{
							foreach (var role in roles)
							{
								if (!IsUserInRole(trx, user, role))
								{
									dbe.ExecuteNonQuery(sql, user, role);
								}
							}
						}
						trx.Commit();
					}
					catch (Exception except)
					{
						trx.Rollback();
						this.TreatException(except, MethodBase.GetCurrentMethod().Name);
					}
				}
			}

		}

		[Obsolete("Prefer other overload with strong typed parameters", false)]
		public override void CreateRole(string roleName)
		{
			try
			{
				Role.Register(roleName, roleName, this.ApplicationName);
			}
			catch (Exception except)
			{
				this.TreatException(except, MethodBase.GetCurrentMethod().Name);
			}
		}

		#region Documentation
		/// <summary>
		/// Cria uma regra e define seu alias de exibição.
		/// </summary>
		#endregion
		public void CreateRole(string roleName, string alias)
		{
			try
			{
				Role.Register(roleName, alias, this.ApplicationName);
			}
			catch (Exception except)
			{
				this.TreatException(except, MethodBase.GetCurrentMethod().Name);
			}
		}

		#region Documentation
		/// <summary>
		/// Cria uma regra incluindo o alias e define a regra pai.
		/// </summary>
		#endregion
		public void CreateRole(string roleName, string alias, Role parent)
		{
			Role.Register(roleName, alias, this.ApplicationName, parent);
		}

		public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
		{
			if (!Role.Exist(roleName, this.ApplicationName))
			{
				throw new ProviderException("Role does not exist.");
			}

			if (throwOnPopulatedRole && GetUsersInRole(roleName).Length > 0)
			{
				throw new ProviderException("Cannot delete a populated role.");
			}

			Role.UnRegister(roleName, this.ApplicationName);

			return true;
		}

		public override string[] FindUsersInRole(string roleName, string usernameToMatch)
		{
			if (string.IsNullOrWhiteSpace(roleName)) throw new ArgumentNullException("roleName");
			if (string.IsNullOrWhiteSpace(usernameToMatch)) usernameToMatch = "%";
			const string sql = @"select users.id, users.class from users inner join usersroles on users.id = usersroles.userId
								 inner join roles on roles.id = usersroles.roleid where rolename = {0} and username like {1}";

			var all = MembershipUserModel.FindBySql(sql, roleName, usernameToMatch);

			return all.Select(s => s.UserName).ToArray();
		}

		public override string[] GetAllRoles()
		{
			return Role.FindAll().Where(s => s.ApplicationName == this.ApplicationName).Select(s => s.RoleName).ToArray();
		}

		public override string[] GetRolesForUser(string username)
		{
			MembershipUserModel user = MembershipUserModel.FindByUserName(username, this.ApplicationName);

			if (user == null)
			{
				return new string[0];
			}
			else
			{
				return Role.FindByUser(user).Select(s => s.RoleName).ToArray();
			}
		}

		public override string[] GetUsersInRole(string roleName)
		{
			return Role.GetUsersByRole(roleName, this.ApplicationName).Select(s => s.UserName).ToArray();
		}

		internal bool IsUserInRole(IDbTransaction trx, string username, string roleName)
		{
			return Convert.ToInt32(
				DBEngine.GetInstance(trx).ExecuteScalar(
				@"select count(*) from UsersRoles UR
				inner join Users U  on U.Id = UR.UserId 
				inner join Roles R on R.Id = UR.RoleId where
				 R.Rolename = {0} and U.Username = {1} and U.ApplicationName = {2}",
														roleName, username, this.ApplicationName)
				) > 0;
		}

		internal bool IsUserInRole(IDbTransaction trx, LightOneMembershipUser user, Role role)
		{
			if (user == null) throw new ArgumentNullException("user");
			if (role == null) throw new ArgumentNullException("role");
			if (user.ApplicationName != role.ApplicationName) throw new ProviderException("Wrong applications between user and role.");

			return Convert.ToInt32(
				DBEngine.GetInstance(trx).ExecuteScalar(@"select count(*) from UsersRoles 
														userid = {0} and roleid = {1}",
														user, role)
				) > 0;
		}

		[Obsolete("Prefer other overload with strong typed parameters", false)]
		public override bool IsUserInRole(string username, string roleName)
		{
			return Convert.ToInt32(
				DBEngine.GetInstance().ExecuteScalar(
				@"select count(*) from UsersRoles UR
				inner join Users U  on U.Id = UR.UserId 
				inner join Roles R on R.Id = UR.RoleId where
				R.Rolename = {0} and U.Username = {1} and U.ApplicationName = {2}",
														roleName, username, this.ApplicationName)
				) > 0;
		}

		public bool IsUserInRole(LightOneMembershipUser user, Role role)
		{
			if (user == null) throw new ArgumentNullException("user");
			if (role == null) throw new ArgumentNullException("role");
			if (user.ApplicationName != role.ApplicationName) throw new ProviderException("Wrong applications between user and role.");

			return Convert.ToInt32(
				DBEngine.GetInstance().ExecuteScalar(@"select count(*) from UsersRoles 
														userid = {0} and roleid = {1}",
														user, role)
				) > 0;
		}

		[Obsolete("Prefer other overload with strong typed parameters", false)]
		public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
		{
			const string sql = @"delete from usersroles where 
						roleid in (select id from roles where Rolename ={1} and ApplicationName = {0})
						and UserId in (select id from users where username = {2} and ApplicationName = {0})";
			using (DBEngine dbe = DBEngine.GetInstance())
			{
				using (IDbTransaction trx = dbe.BeginTransaction())
				{
					try
					{
						foreach (var username in usernames)
						{
							foreach (var role in roleNames)
							{
								dbe.ExecuteNonQuery(sql, this.ApplicationName, role, username);
							}
						}

						trx.Commit();
					}
					catch (Exception except)
					{
						trx.Rollback();
						this.TreatException(except, MethodBase.GetCurrentMethod().Name);
					}
				}
			}
		}

		public void RemoveUsersFromRoles(LightOneMembershipUser[] users, Role[] roles)
		{

			const string sql = @"delete from usersroles where 
						roleid = {1} and ApplicationName = {0})
						and UserId  = {2} and ApplicationName = {0})";
			using (DBEngine dbe = DBEngine.GetInstance())
			{
				using (IDbTransaction trx = dbe.BeginTransaction())
				{
					try
					{
						foreach (var user in users)
						{
							foreach (var role in roles)
							{
								dbe.ExecuteNonQuery(sql, this.ApplicationName, role, user);
							}
						}

						trx.Commit();
					}
					catch (Exception except)
					{
						trx.Rollback();
						this.TreatException(except, MethodBase.GetCurrentMethod().Name);
					}
				}
			}
		}

		public override bool RoleExists(string roleName)
		{
			return Role.Exist(roleName, this.ApplicationName);
		}

		private void TreatException(Exception e, string action)
		{
			if (this.WriteExceptionsToEventLog)
			{
				EventLog log = new EventLog();
				log.Source = eventSource;
				log.Log = eventLog;

				string message = exceptionMessage + "\n\n";
				message += "Action: " + action + "\n\n";
				message += "Exception: " + e.ToString();

				log.WriteEntry(message);
			}
			else
			{
				throw new Exception(string.Format("{0}:{1}", action, e.Message), e);
			}
		}
	}
}